#ifndef LOG_H
#define LOG_H

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <time.h>
#include <iomanip>

#include "StringBuilder.h"

class Log {
public:

	enum LOG_LEVEL {
		LEVEL_NONE, // no logging
		LEVEL_FATAL, // log errors that are fatal for the application
		LEVEL_ERROR, // log errors
		LEVEL_WARN, // log warnings
		LEVEL_INFO, // log informations
		LEVEL_DEBUG, // log everything

		NUMBER_OF_LEVELS // overall number of log levels
	};

	enum LOG_TARGET {
		TARGET_CONSOLE, // write log to console
		TARGET_FILE, // write log to file

		NUMBER_OF_TARGETS // overall number of possible log targets
	};

protected:
	LOG_LEVEL targetLevels[NUMBER_OF_TARGETS];
	std::string levelNames[NUMBER_OF_LEVELS];
	time_t rawtime;
	std::ofstream outputFileStream;

	virtual void Init(const std::string &outputFile) {
		SetLogLevel(TARGET_CONSOLE, LEVEL_DEBUG);
		SetLogLevel(TARGET_FILE, LEVEL_WARN);

		levelNames[LEVEL_NONE] = "NONE";
		levelNames[LEVEL_FATAL] = "FATAL";
		levelNames[LEVEL_ERROR] = "ERROR";
		levelNames[LEVEL_WARN] = "WARN";
		levelNames[LEVEL_INFO] = "INFO";
		levelNames[LEVEL_DEBUG] = "DEBUG";

		outputFileStream.open(outputFile.c_str(), std::ios_base::app);
	}

	std::string GetTimeString() {
		time(&rawtime);
		struct tm ti;
		localtime_s(&ti, &rawtime);
		std::stringstream ss;
		ss << "[" <<
			ti.tm_year + 1900 << "-" << std::setfill<char>('0') <<
			std::setw(2) << ti.tm_mon << "-" <<
			std::setw(2) << ti.tm_mday << " " <<
			std::setw(2) << ti.tm_hour << ":" <<
			std::setw(2) << ti.tm_min << ":" <<
			std::setw(2) << ti.tm_sec << "]";
		return ss.str();
	}

	virtual void LogForLevel(LOG_LEVEL level, const std::string &s) {
		if (targetLevels[TARGET_CONSOLE] >= level) {
			std::cout
				<< std::endl
				<< GetTimeString() << ' '
				<< std::setfill<char>(' ') << std::setw(5) << levelNames[level]
				<< ": "
				<< s << std::endl;
		}

		if (targetLevels[TARGET_FILE] >= level) {
			outputFileStream
				<< std::endl
				<< GetTimeString() << ' '
				<< std::setfill<char>(' ') << std::setw(5) << levelNames[level]
				<< ": "
				<< s << std::endl;
		}
	}

public:

	Log() {
		Init("log.txt");
	}

	Log(std::string outputFile) {
		Init(outputFile);
	}

	virtual ~Log() {
		if (outputFileStream.is_open()) {
			outputFileStream.close();
		}
	}

	void SetLogLevel(LOG_TARGET target, LOG_LEVEL level) {
		targetLevels[target] = level;
	}

	inline void LogFatal(const std::string &s) {
		LogForLevel(LEVEL_FATAL, s);
	}

	inline void LogError(const std::string &s) {
		LogForLevel(LEVEL_ERROR, s);
	}

	inline void LogWarn(const std::string &s) {
		LogForLevel(LEVEL_WARN, s);
	}

	inline void LogInfo(const std::string &s) {
		LogForLevel(LEVEL_INFO, s);
	}

	inline void LogDebug(const std::string &s) {
		LogForLevel(LEVEL_DEBUG, s);
	}
};

#endif //LOG_H
